/**
 * Anderground project - Subway maps processor application.
 * Copyright (C) 2008-2009 Eldar Abusalimov, Ilya Shurigyn
 * 
 * This file is part of Anderground.
 * 
 * Anderground is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * Anderground is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Anderground.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.anderground.core.model;

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

import org.anderground.core.util.Bindable;
import org.anderground.core.util.Binder;
import org.anderground.core.util.BindingException;

/**
 * @author Ilya Shurigyn (honorik@gmail.com)
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 */
public class Hub extends Bindable {

	/** unmodifiable set of this hub's {@link Station}s */
	private final Set<Station> stations;

	/**
	 * Searches all neighbor transfers graph and creates a new hub if necessary.
	 * 
	 * @param station
	 *            the station enter-point to search for transfers
	 * @return If the station had no hub yet however had any transfers then new
	 *         hub is created and returned. Otherwise null is returned.
	 */
	public static Hub createHubIfNeeded(Station station) {
		if (station.getBoundHub() != null) {
			return null;
		}

		Set<Station> hubStations = new HashSet<Station>();
		LinkedList<Station> neighbors = new LinkedList<Station>();

		neighbors.add(station);

		while (!neighbors.isEmpty()) {
			station = neighbors.removeFirst();
			if (hubStations.contains(station)) {
				continue;
			}
			hubStations.add(station);

			for (Transition transition : station.getBoundTransitions()) {
				if (!(transition instanceof Transfer)) {
					continue;
				}
				Transfer boundTransfer = (Transfer) transition;

				Station fromStation = boundTransfer.getFromStation();
				if (station != fromStation) {
					if (!neighbors.contains(fromStation)) {
						neighbors.add(fromStation);
					}
				}
				Station toStation = boundTransfer.getToStation();
				if (station != toStation) {
					if (!neighbors.contains(toStation)) {
						neighbors.add(toStation);
					}
				}
			}
		}

		if (hubStations.size() < 2) {
			// we do not create a hub for single stations
			return null;
		}
		return new Hub(hubStations);
	}

	/**
	 * @param stations
	 */
	private Hub(Set<Station> stations) {
		this.stations = Collections.unmodifiableSet(stations);
	}

	@Override
	protected void onBindTo(Binder binder) throws BindingException {
		for (Station station : stations) {
			station.bindWith(this);
			for (Transfer transfer : station.getTransfers()) {
				transfer.bindWith(this);
			}
		}
	}

	@Override
	protected void onUnbindFrom(Binder binder) throws BindingException {
		for (Station station : stations) {
			station.unbindWith(this);
			for (Transfer transfer : station.getTransfers()) {
				transfer.unbindWith(this);
			}
		}
	}

	/**
	 * @return the unmodifiable set of this hub's {@link Station}s
	 */
	public Set<Station> getStations() {
		return stations;
	}

	@Override
	public String toString() {
		String str = null;
		for (Station station : stations) {
			if (str == null) {
				str = "hub of " + station;
			} else {
				str += ", " + station;
			}
		}
		return (str != null) ? str : "empty hub";
	}

}
